package raft

//
// this is an outline of the API that raft must expose to
// the service (or tester). see comments below for
// each of these functions for more details.
//
// rf = Make(...)
//   create a new Raft server.
// rf.Start(command interface{}) (index, term, isleader)
//   start agreement on a new log entry
// rf.GetState() (term, isLeader)
//   ask a Raft for its current term, and whether it thinks it is leader
// ApplyMsg
//   each time a new entry is committed to the log, each Raft peer
//   should send an ApplyMsg to the service (or tester)
//   in the same server.
//

import (
	"sync"
	"time"
	"os"
	"runtime"
	"log"
	"strings"
	"fmt"
)


// as each Raft peer becomes aware that successive log entries are
// committed, the peer should send an ApplyMsg to the service (or
// tester) on the same server, via the applyCh passed to Make(). set
// CommandValid to true to indicate that the ApplyMsg contains a newly
// committed log entry.
//
// in part 3D you'll want to send other kinds of messages (e.g.,
// snapshots) on the applyCh, but set CommandValid to false for these
// other uses.

const (
	FOLLOWER = 0
	LEADER = 1
	CANDIDATE = 2
)

const (
	ElectionTimeoutMin = 150
	ElectionTimeoutMax = 350
	KeepAlivePeriod = 50
)

type SyncMutex struct {
	mu	sync.Mutex
	cond	*sync.Cond
	lno	int
	locked	bool
	me	int
}


func (sm *SyncMutex)Init(me int) {
	//log.Printf("SyncMutex Init\n")
	sm.cond = sync.NewCond(&sm.mu)
	sm.lno = 0
	sm.me = me
	sm.locked = false
}

func (sm *SyncMutex)Lock() {
	sm.mu.Lock()
	defer sm.mu.Unlock()
	for sm.locked {
		ch := make(chan bool)
		go func() {
			sm.cond.Wait()
			ch<- true
		}()
		select {
		case <-ch:
		case <-time.After(2*time.Second):
			log.Printf("Deadlock has been detected, lno=%d, me=%d\n",
				sm.lno, sm.me)
		}
	}
	_, _, line, _ := runtime.Caller(1)
	sm.lno =  line
	sm.locked = true
}

func (sm *SyncMutex)Unlock() {
	sm.mu.Lock()
	defer sm.mu.Unlock()

	sm.locked = false
	sm.lno = 0
	sm.cond.Signal()
}

func (sm *SyncMutex)ShouldBeLocked() {
	sm.mu.Lock()
	defer sm.mu.Unlock()

	if !sm.locked {
		_, file, line, _ := runtime.Caller(2)
		log.Printf("Error: mutex should be locked, but it is not.\n")
		log.Printf("the caller is at %s:%d\n", file, line)
		os.Exit(3)
	}
}

func (sm *SyncMutex)Show() {
	log.Printf("SyncMutex: me=%d,locked=%v,lno=%d",
		sm.me, sm.locked, sm.lno)
}

func Min(x, y int) int {
	if x < y {
		return x
	}
	return y
}

func Max(x, y int) int {
	if x > y {
		return x
	}
	return y
}

var (
	TimeStart time.Time
	HasTime bool
	chLog chan string
)

func getPos() string {
	fname := ""
	pc, _, line, _ := runtime.Caller(2)
	fc := runtime.FuncForPC(pc)
	if fc != nil {
		tmp := fc.Name()
		words := strings.Split(tmp, ".")
		fname = fmt.Sprintf("%s:%d", words[len(words)-1], line)
	} else {
		fname = fmt.Sprintf("Null(): %d", line)
	}

	return fname
}

func SprintCmd(cmd interface{}) string {
	c := ""
	switch v := cmd.(type) {
	case string:
		sz := len(v)
		if sz > 4 {
			sz = 4
		}
		c = fmt.Sprintf("%s", v[:sz])
	case int:
		c = fmt.Sprintf("%d", (v%1000))
	default:
	}
	return c
}

func SprintEntry(e *logEntry) string {
	c := SprintCmd(e.Command)
	s := fmt.Sprintf("{%d,%d,%s}", e.Index, e.Term, c)
	return s
}

func SprintLog(entries []logEntry) string {
	s := ""
	s += fmt.Sprintf("[")
	n := len(entries)
	for i:=0; i<n; i++ {
		e := entries[i]
		cmd := e.Command
		c := SprintCmd(cmd)
		if i<2 || i>n-3 {
			s += fmt.Sprintf("{%d,%d,%s},", e.Index, e.Term, c)
		} else if n>5 && i==3 {
			s += "..."
		}
	}
	s += fmt.Sprintf("]")
	return s
}

var stName = [3]string{"FL", "LD", "CD"}

func (rf *Raft) Dbg(format string, a ...interface{}) {
	if !Debug {
		return
	}

	if !HasTime {
		TimeStart = time.Now()
		HasTime = true
	}

	td := time.Since(TimeStart)
	tmp := td.Microseconds()
	sec := tmp / 1000000
	us := tmp % 1000000

	fname := getPos()

	s1 := ""
	if rf.state == LEADER {
		s1 = fmt.Sprintf("\n\t\tNI=%v, MI=%v", rf.nextIndex, rf.matchIndex)
	}
	log := ""
	s := fmt.Sprintf(format, a...)
	log += fmt.Sprintf("\n%d.%06d %-10s me=%d,tm=%d,st=%s,vf=%d,lg=%d," +
		"apld=%d,cmt=%d%s\n\t\tlog=",
		sec, us, fname, rf.me, rf.currentTerm, stName[rf.state],
		rf.votedFor, len(rf.log), rf.lastApplied, rf.commitIndex, s1)
	log += fmt.Sprintf(SprintLog(rf.log))
	log += fmt.Sprintf("\n\t\t%s", s)
	chLog<-log
}

func dumpLog() {
	for {
		s := <-chLog
		fmt.Printf(s)
	}
}

func (rf *Raft) assert(cond bool) {
	if !cond {
		fname := getPos()
		rf.Dbg("assert\n")
		fmt.Printf("Assertion failed at %s\n", fname)
		panic("assertion failed")
	}
}

func (rf *Raft) assertf(cond bool, format string, a ...interface{}) {
	if !cond {
		fname := getPos()
		rf.Dbg(format, a...)
		fmt.Printf("Assertion failed at %s\n", fname)
		panic("assertion failed")
	}
}

func (ae *AppendEntriesArgs)Sprint() string {
	s := fmt.Sprintf("{tm=%d, LdId=%d, PrLgIdx=%d, PrLgTm=%d, CmtIdx=%d, ",
		ae.Term, ae.LeaderID, ae.PrevLogindex, ae.PrevLogterm,
		ae.CommittedIndex)
	n := len(ae.Entries)
	if n == 0 {
		s += "[]}"
	} else {
		s += SprintLog(ae.Entries)
		s += "}"
	}
	return s
}

